package org.stvd.repository.base.impl;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.hibernate.SQLQuery;
import org.hibernate.transform.Transformers;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.stvd.core.dto.QueryResult;
import org.stvd.repository.base.BaseDao;

@SuppressWarnings({ "unchecked", "deprecation" })
@Repository("BaseDao")
public class BaseDaoImpl<T> implements BaseDao<T> {

    private Class<T> classt;

    public BaseDaoImpl() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            this.setClasst((Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0]);
        } else {
            this.setClasst(null);
        }
    }

    @PersistenceContext
    protected EntityManager em;

    @Override
    public T findByPk(Class<T> t, Object pk) {
        return (T) em.find(t, pk);
    }
    
    public List<T> findAll(Class<T> t) {
        return em.createQuery("select o from " + getEntityName(t) + " o ").getResultList();
    }
    
    protected String getEntityName(Class<T> entityClass) {
        String entityname = entityClass.getSimpleName();
        Entity entity = entityClass.getAnnotation(Entity.class);
        if (entity.name() != null && !"".equals(entity.name())) {
            entityname = entity.name();
        }
        return entityname;
    }
    
    @Override
    @Transactional
    public void insert(T entity) {
        em.persist(entity);
    }

    @Override
    @Transactional
    public void update(T entity) {
        em.merge(entity);
    }
    
    @Override
    public void delete(Class<T> t, Object pk) {
        em.remove(findByPk(t, pk));
    }
   
    @Override
    public long getCountByHQL(String hql, Object... params) {
        Query query = em.createQuery(hql);
        for (int i = 0; params != null && i < params.length; i++) {
            query.setParameter(i, params[i]);
        }
        return Long.parseLong(query.getSingleResult().toString());
    }
    
    @Override
    public long getCountByHQL(String hql, Map<String, Object> params) {
        Query query = em.createQuery(hql);
        if (params != null && !params.isEmpty()) {
            for (Entry<String, Object> map : params.entrySet()) {
                query.setParameter(map.getKey(), map.getValue());
            }
        }
        Object obj = query.getSingleResult();
        return obj != null ? Long.parseLong(obj.toString()) : 0;
    }
    
    @Override
    public long getCountBySQL(String hql, Object... params) {
        Query query = em.createNativeQuery(hql);
        for (int i = 0; params != null && i < params.length; i++) {
            query.setParameter(i, params[i]);
        }
        return Long.parseLong(query.getSingleResult().toString());
    }

    @Override
    public long getCountBySQL(String sql, Map<String, Object> params) {
        Query query = em.createNativeQuery(sql);
        if (params != null && !params.isEmpty()) {
            for (Entry<String, Object> map : params.entrySet()) {
                query.setParameter(map.getKey(), map.getValue());
            }
        }
        return Long.parseLong(query.getSingleResult().toString());
    }
    
    @Override
    public List<T> findByHQL(String hql, Object... params) {
        Query query = em.createQuery(hql);
        for (int i = 0; params != null && i < params.length; i++) {
            query.setParameter(i, params[i]);
        }
        return query.getResultList();
    }
    
    @Override
    public List<T> findByHQL(String hql, Map<String, Object> params) {
        Query query = em.createQuery(hql);
        if (params != null && !params.isEmpty()) {
            for (Entry<String, Object> map : params.entrySet()) {
                query.setParameter(map.getKey(), map.getValue());
            }
        }
        return query.getResultList();
    }
    
    @Override
    public List<T> findByHQL(int start, int limit, String hql, Object... params) {
        Query query = em.createQuery(hql).setFirstResult(start).setMaxResults(limit);
        for (int i = 0; params != null && i < params.length; i++) {
            query.setParameter(i, params[i]);
        }
        return query.getResultList();
    }
    
    @Override
    public List<T> findByHQL(int start, int limit, String hql, Map<String, Object> params) {
        Query query = em.createQuery(hql).setFirstResult(start).setMaxResults(limit);
        if (params != null && !params.isEmpty()) {
            for (Entry<String, Object> map : params.entrySet()) {
                query.setParameter(map.getKey(), map.getValue());
            }
        }
        return query.getResultList();
    }
    
    @Override
    public List<Object[]> findBySQL(String sql, Object... params) {
        Query query = em.createNativeQuery(sql);
        for (int i = 0; params != null && i < params.length; i++) {
            query.setParameter(i, params[i]);
        }
        return query.getResultList();
    }

    @Override
    public List<Object[]> findBySQL(int start, int limit, String sql, Object... params) {
        Query query = em.createNativeQuery(sql).setFirstResult(start).setMaxResults(limit);
        for (int i = 0; params != null && i < params.length; i++) {
            query.setParameter(i, params[i]);
        }
        return query.getResultList();
    }
    
    @Override
    public List<Map<String, Object>> findBySQLToMap(String sql, Map<String, Object> params) {
        Query query = em.createNativeQuery(sql);
        if (params != null && !params.isEmpty()) {
            for (Entry<String, Object> map : params.entrySet()) {
                query.setParameter(map.getKey(), map.getValue());
            }
        }
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        return query.getResultList();
    }
    
    @Override
    public List<Map<String, Object>> findBySQLToMap(int start, int limit, String sql, Map<String, Object> params) {
        Query query = em.createNativeQuery(sql).setFirstResult(start).setMaxResults(limit);
        if (params != null && !params.isEmpty()) {
            for (Entry<String, Object> map : params.entrySet()) {
                query.setParameter(map.getKey(), map.getValue());
            }
        }
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        return query.getResultList();
    }
    
    @Override
    public QueryResult<T> getQueryResultByHQL(int start, int limit, String hql, Object... params) {
        return getQueryResultByHQLPublic(start, limit, hql, null, params);
    }
    
    @Override
    public QueryResult<T> getQueryResultByHQL(int start, int limit, String hql, Map<String, Object> params) {
        return getQueryResultByHQLPublic(start, limit, hql, params, "");
    }
    
    @Override
    public QueryResult<Object[]> getQueryResultBySQL(int start, int limit, String sql, Object... params) {
        QueryResult<Object[]> queryResult = new QueryResult<Object[]>();
        String countSQL = "select count(*) ";
        if (sql.toLowerCase().indexOf("order by") > -1) {
            countSQL += sql.substring(sql.toLowerCase().indexOf("from"), sql.toLowerCase().indexOf("order by"));
        } else {
            countSQL += sql.substring(sql.toLowerCase().indexOf("from"));
        }
        long total = getCountBySQL(countSQL, params);
        queryResult.setPageCount((int) Math.ceil(total * 1d / limit));
        queryResult.setPageIndex((int) Math.floor(start * 1d / limit) + 1);
        if (queryResult.getPageCount() > 0 && queryResult.getPageCount() < queryResult.getPageIndex()) {
            queryResult.setPageIndex(queryResult.getPageIndex() - 1);
        }
        queryResult.setTotal(total);
        if (queryResult.getTotal() > 0) {
            queryResult.setRows(findBySQL((queryResult.getPageIndex() - 1) * limit, limit, sql, params));
        }
        return queryResult;
    }
    
    @Override
    public QueryResult<Map<String, Object>> getQueryResultBySQLToMap(int start, int limit, String sql,
            Map<String, Object> params) {
        QueryResult<Map<String, Object>> queryResultMap = new QueryResult<Map<String, Object>>();
        String countSQLHeard = "select count(*) ";
        if (sql.toLowerCase().indexOf("order by") > -1) {
            countSQLHeard += sql.substring(sql.toLowerCase().indexOf("from"), sql.toLowerCase().indexOf("order by"));
        } else {
            countSQLHeard += sql.substring(sql.toLowerCase().indexOf("from"));
        }
        long total = getCountBySQL(countSQLHeard, params);
        queryResultMap.setPageCount((int) Math.ceil(total * 1d / limit));
        queryResultMap.setPageIndex((int) Math.floor(start * 1d / limit) + 1);
        if (queryResultMap.getPageCount() > 0 && queryResultMap.getPageCount() < queryResultMap.getPageIndex()) {
            queryResultMap.setPageIndex(queryResultMap.getPageIndex() - 1);
        }
        queryResultMap.setTotal(total);
        if (queryResultMap.getTotal() > 0) {
            queryResultMap.setRows(findBySQLToMap((queryResultMap.getPageIndex() - 1) * limit, limit, sql, params));
        }
        return queryResultMap;
    }
    
    public QueryResult<T> getQueryResultByHQLPublic(int start, int limit, String hql, Map<String, Object> mapParams, Object... objectParams) {
        QueryResult<T> queryResult = new QueryResult<T>();
        String countSQL = "select count(*) ";
        if (hql.toLowerCase().indexOf("order by") > -1) {
            countSQL += hql.substring(hql.toLowerCase().indexOf("from"), hql.toLowerCase().indexOf("order by"));
        } else {
            countSQL += hql.substring(hql.toLowerCase().indexOf("from"));
        }
        long total = 0;
        if (mapParams != null && !mapParams.isEmpty()) {
            total = getCountByHQL(countSQL, mapParams);
        } else {
            total = getCountByHQL(countSQL, objectParams);
        }
        queryResult.setPageCount((int) Math.ceil(total * 1d / limit));
        queryResult.setPageIndex((int) Math.floor(start * 1d / limit) + 1);
        if (queryResult.getPageCount() > 0 && queryResult.getPageCount() < queryResult.getPageIndex()) {
            queryResult.setPageIndex(queryResult.getPageIndex() - 1);
        }
        queryResult.setTotal(total);
        if (queryResult.getTotal() > 0) {
            if (mapParams != null && !mapParams.isEmpty()) {
                queryResult.setRows(findByHQL((queryResult.getPageIndex() - 1) * limit, limit, hql, mapParams));
            } else {
                queryResult.setRows(findByHQL((queryResult.getPageIndex() - 1) * limit, limit, hql, objectParams));
            }
        }
        return queryResult;
    }

    @Override
    @Transactional
    public int execSQL(String sql) {
        return em.createNativeQuery(sql).executeUpdate();
    }
    
    @Override
    @Transactional
    public int execHQL(String hql) {
        return em.createQuery(hql).executeUpdate();
    }

    public Class<T> getClasst() {
        return classt;
    }

    public void setClasst(Class<T> classt) {
        this.classt = classt;
    }
}
